#include "level.h"
#include "common.h"

#include <gccore.h>
#include <wiiuse/wpad.h>

using namespace Common;

extern u8 portal_a_png[];
extern u8 portal_b_png[];

extern u8 open_portal_a_png[];
extern u8 open_portal_b_png[];

extern u8 wall_portal_a_png[];
extern u8 wall_portal_b_png[];

extern u8 fire_png[];

extern u8 levelComplete_png[];

extern u8 highScore_png[];

extern u8 dead_png[];

extern u8 sight_a_png[];
extern u8 sight_b_png[];
extern u8 sight_both_png[];
extern u8 sight_none_png[];

Level::Level(SoundManager* ptrSndManager, Settings* settingsPtr)
{
    _ptrSettings = settingsPtr;

	_ptrSndManager = ptrSndManager;
	_world   = 0;
	_portalA = 0;
	_portalB = 0;
	_portii  = 0;
	
	_timeStep        = 1.0f / 60.0f;
	_iterations      = 10;
    _screenOffsetX   = 0;
    _screenOffsetY   = 0;
		
	_doDrawSight = true;
	_sightX = 0;
	_sightY = 0;
	
	background = new Background();
	
	_hud = new PortalHud();
	_textBubble = new TextBubble();
	
	Image* fireImage = new Image();
	if(fireImage->LoadImage(fire_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
	_fireSprite.SetImage(fireImage);
	_fireSprite.SetPosition(-2000,WORLD_DEAD_HEIGHT);
	
	_cinematic = true;
	
	_mode = GameMode::STORY;
	
	_dead = false;
	
	_won = false;
	
	_completeImage = new Image();
	if(_completeImage->LoadImage(levelComplete_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
	_scoreImage = new Image();
	if(_scoreImage->LoadImage(highScore_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
	_deadImage = new Image();
	if(_deadImage->LoadImage(dead_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
	portal_a_Image = new Image();
	portal_b_Image = new Image();
	
	open_portal_a_Image = new Image();
	open_portal_b_Image = new Image();
	
	wall_portal_a_Image = new Image();
	wall_portal_b_Image = new Image();
		
    if(portal_a_Image->LoadImage(portal_a_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    if(portal_b_Image->LoadImage(portal_b_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
    if(open_portal_a_Image->LoadImage(open_portal_a_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    if(open_portal_b_Image->LoadImage(open_portal_b_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
    if(wall_portal_a_Image->LoadImage(wall_portal_a_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    if(wall_portal_b_Image->LoadImage(wall_portal_b_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
	
	_randomJunk = new RandomJunk();
	
	sight_a_Image = new Image();
	sight_b_Image = new Image();
	sight_both_Image = new Image();
	sight_none_Image = new Image();
	
	if(sight_a_Image->LoadImage(sight_a_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    if(sight_b_Image->LoadImage(sight_b_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    if(sight_both_Image->LoadImage(sight_both_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    if(sight_none_Image->LoadImage(sight_none_png, IMG_LOAD_TYPE_BUFFER) != IMG_LOAD_ERROR_NONE)exit(0);
    
}

Level::~Level()
{
	CleanUp();
}

void Level::Reset()
{
	_won = false;
	
	_dead = false;
	

	//Reset Level
	SetStartPosition (_sx, _sy);
	
	for(int i = 0; i < (int)_cakes.size(); i++)
		_cakes[i]->SetAvailable(true);
	
	_hud->SetTotalCakes((int)_cakes.size());
	_hud->SetCakeObtained(0);
	_hud->SetPortalsSpawned(0);
	_hud->InitTime();
	
	_portii->Reset();
	
	_portalB->SetEnabled(false);
	_portalA->SetEnabled(false);
}

void Level::CleanUp()
{
        for(int i = 0; i < (int)_cakes.size(); i++)
              if(_cakes[i])
                 delete _cakes[i];

        for(int i = 0; i < (int)_npZones.size(); i++)
              if(_npZones[i])
                 delete _npZones[i];

        for(int i = 0; i < (int)_transGround.size(); i++)
              if(_transGround[i])
                 delete _transGround[i];

        for(int i = 0; i < (int)_ground.size(); i++)
              if(_ground[i])
                 delete _ground[i];

        for(int i = 0; i < (int)_storyText.size(); i++)
              if(_storyText[i])
                 delete _storyText[i];

	_cakes.clear();
	_npZones.clear();
	_transGround.clear();
	_ground.clear();
	_storyText.clear();
	_storyIndex = 0;
	
	if(_portii != NULL)
	{
		delete _portii;
		_portii = NULL;
	}
	
	if(_world != NULL)
	{
		//Destroy World
		delete _world;
		_world = NULL;
	}
	
	if(_portalA != NULL)
	{
		delete _portalA;
		_portalA = NULL;
	}
	
	if(_portalB != NULL)
	{
		delete _portalB;
		_portalB = NULL;
	}

}

void Level::Init(LevelBuilder* lvl)
{
   _won = false;
	
   int screen_width, screen_height;

	lvl->CleanPhysics();

	CleanUp();
	
  /*-------------
     Create World
	-------------
  */
	b2AABB worldAABB;
	// Visible area is 8x6 units
	worldAABB.lowerBound.Set(-200.0f, -200.0f);
	worldAABB.upperBound.Set(200.0f, 200.0f);

	b2Vec2 gravity(0.0f, 8.0f);
	
	bool doSleep = false;
	_world = new b2World(worldAABB, gravity, doSleep);
	
	// Get Screen Resolution
	GXRModeObj *rmode = VIDEO_GetPreferredMode(NULL);
	screen_width = (int)((float)rmode->viWidth * 3.2f); // <- works for me
	screen_height = (int)((float)rmode->viHeight * 3.2f);
	float32 width = ((float)screen_width / PIXELS_PER_UNIT)*2.0;
	float32 height = ((float)screen_height / PIXELS_PER_UNIT)*2.0;
	
	b2BodyDef groundBodyDef;
	groundBodyDef.position.Set(-1*(width/2.0), -1*(height/2.0));
	b2Body* groundBody = _world->CreateBody(&groundBodyDef);

	b2PolygonDef groundShapeDef[4];
	groundShapeDef[0].SetAsBox(float32(width), float32(0.1f),   b2Vec2(width/2.0, -0.1),       0.0);
	groundShapeDef[1].SetAsBox(float32(width), float32(0.1f),   b2Vec2(width/2.0, height),     0.0);
	groundShapeDef[2].SetAsBox(float32(0.1f),  float32(height), b2Vec2(-0.1,      height/2.0), 0.0);
	groundShapeDef[3].SetAsBox(float32(0.1f),  float32(height), b2Vec2(width,     height/2.0), 0.0);
	
	for(int i=0;i<4;++i)
		groundBody->CreateShape(&groundShapeDef[i]);
		
	/*-------------
	  Create Portii (Player)
	*/
	_portii = new Portii(_world);
	
	/*------------
	  Create Portal
	*/
	_portalA = new Portal(open_portal_a_Image, portal_a_Image, wall_portal_a_Image);
	_portalB = new Portal(open_portal_b_Image, portal_b_Image, wall_portal_b_Image);
	
	SetStartPosition(lvl->GetStartX(), lvl->GetStartY());

    lvl->Phyisicize(_world);
	
	lvl->ConstructCakes(&_cakes);
	
	lvl->ConstructNpZones(&_npZones);
	
	lvl->ConstructGround(&_ground);
	
	lvl->ConstructTransGround(&_transGround);
	
	lvl->ConstructStoryText(&_storyText);
	
	_hud->SetGameMode(_mode);

	_hud->SetTotalCakes((int)_cakes.size());
	_hud->SetCakeObtained(0);
	_hud->SetPortalsSpawned(0);
	_hud->InitTime();

        Reset();
}

bool Level::Update(Pointer* ptr)
{
	WPAD_ScanPads();
		
	u16 btnsheld = WPAD_ButtonsHeld(0);
	u16 btnsdown = WPAD_ButtonsDown(0);
	
    b2Vec2 v = _portii->GetBody()->GetPosition();     

    _screenOffsetX = -1*v.x*PIXELS_PER_UNIT + 320.0;
        
    _screenOffsetY = -1*v.y*PIXELS_PER_UNIT + 290.0;
	
	WPADData* wpd = WPAD_Data(0);
	
	if(btnsdown & WPAD_BUTTON_HOME)
	{
		return false;
	}
	
	if(btnsheld & WPAD_BUTTON_LEFT)
	{
        _portii->Accel(-0.1);
	}
	else if(btnsheld & WPAD_BUTTON_RIGHT)
	{
        _portii->Accel(0.1);
	}
	else
	{
		/* show events specific to supported expansions */
		if (wpd->exp.type == EXP_NUNCHUK) {
			/* nunchuk */
			struct nunchuk_t* nc = (nunchuk_t*)&wpd->exp.nunchuk;

			float ang = (((-1.0f*(nc->js.ang))+360.0f)+90.0f);

			if(nc->js.mag > 0)
			{
				if( ang > 300 ||  ang < 45)
				{
					_portii->Accel(0.1);
				}
				else if( ang < 225 &&  ang > 135)
				{
					_portii->Accel(-0.1);
				}
				else
				{
					_portii->SlowToStop();
				}
			}
			else
			{
				_portii->SlowToStop();
			}
			
			//TODO Fix this so advanced controls actually does something
			if(_ptrSettings->IsAdvancedControls())
			{
				if(nc->orient.roll > 0.1)
				{
					_portii->Accel(0.1);
				}
				else if(nc->orient.roll < -0.1)
				{
					_portii->Accel(-0.1);
				}
			}
			
		}
		else
		{
			//Roll to a stop
			_portii->SlowToStop();
		}
	}
	
	_portii->AimLocation(ptr->GetX()-_screenOffsetX, ptr->GetY()-_screenOffsetY);
	
	if(btnsdown & WPAD_BUTTON_A || btnsdown & WPAD_BUTTON_B)
	{
		_ptrSndManager->PlaySound(Sound::PORTAL_GUN_FX, 0);
	}
		
	if(PortalWillStick(ptr))
	{
		Sprite*	attachedWallSprite = NULL;
		
		attachedWallSprite = GetPortalSprite(ptr);
		
		if(btnsdown & WPAD_BUTTON_A)
		{
			_hud->PortalSpawned();
			if(attachedWallSprite == NULL)
			{
				_portalA->SetLocation(ptr->GetX()-_screenOffsetX, ptr->GetY()-_screenOffsetY);
				_portalA->SetRotation(0);
				_portalA->SetEnabled(true);
				_portalA->SetWalled(false);
				
			}
			else
			{
				_portalA->SetLocation(ptr->GetCenterX()-_screenOffsetX, ptr->GetCenterY()-_screenOffsetY);
				_portalA->SetAngle(attachedWallSprite->GetRotation());
				_portalA->SetWalled(true);
				_portalA->SetEnabled(true);
			}
			
		}
		else if(btnsdown & WPAD_BUTTON_B)
		{
			_hud->PortalSpawned();
			if(attachedWallSprite == NULL)
			{
				_portalB->SetLocation(ptr->GetX()-_screenOffsetX, ptr->GetY()-_screenOffsetY);
				_portalB->SetRotation(0);
				_portalB->SetEnabled(true);
				_portalB->SetWalled(false);
			}
			else
			{
				_portalB->SetLocation(ptr->GetCenterX()-_screenOffsetX, ptr->GetCenterY()-_screenOffsetY);
				_portalB->SetAngle(attachedWallSprite->GetRotation());
				_portalB->SetWalled(true);
				_portalB->SetEnabled(true);
			}
		}
		
		_doDrawSight = true;
		_sightX = ptr->GetX()+ptr->GetWidth()/2.0;
		_sightY = ptr->GetY()+ptr->GetHeight()/2.0;
	}
	else
	{
		_doDrawSight = false;
	}
	
	if(_portalB->IsSet() && _portalA->IsSet())
	{
		_portalB->SetOpen(true);
		_portalA->SetOpen(true);
		ptr->SetImage(sight_both_Image);
	}
	else
	{
		if(_portalB->IsSet())
		{
			ptr->SetImage(sight_b_Image);
		}
		else if(_portalA->IsSet())
		{
			ptr->SetImage(sight_a_Image);
		}
		else
		{
			ptr->SetImage(sight_none_Image);
		}
		
		_portalB->SetOpen(false);
		_portalA->SetOpen(false);
	}
			
	_portii->Update();
		
	if(_portalA->IsSet() && _portalB->IsSet())
	{
		if(_portalA->IsInPortal(_portii->GetCenter()) == 1)
		{
			//_ptrSndManager->PlaySound(Sound::PORTAL_ENTER_FX, 0);
			//Entered Portal A, so move to Portal B.
			_portii->EnterPortal(_portalB);
			_portalB->SetInPortal(false);
		}
		else if(_portalB->IsInPortal(_portii->GetCenter()) == 1)
		{
			//_ptrSndManager->PlaySound(Sound::PORTAL_ENTER_FX, 0);
			//Entered Portal B, so move to Portal A.
			_portii->EnterPortal(_portalA);
			_portalA->SetInPortal(false);
		}
	}
	//
	
	CheckCakeHits();
	
	CheckForDeadPlayer();

	
	if(_mode == GameMode::STORY)
	{
		if(_storyIndex >= (int)_storyText.size())
		{
			_world->Step(_timeStep, _iterations, _iterations);	
		}
	}
	else
	{
		_world->Step(_timeStep, _iterations, _iterations);	
	}
	
	if (wpd->exp.type == EXP_NUNCHUK) 
	{
			/* nunchuk */
			struct nunchuk_t* nc = (nunchuk_t*)&wpd->exp.nunchuk;

			if (IS_JUST_PRESSED(nc, NUNCHUK_BUTTON_C))
			{
				//Show/Hide Designator
				_portii->EnableLaser(!(_portii->IsLaserEnabled()));
			}
	
			if (IS_PRESSED(nc, NUNCHUK_BUTTON_Z))
			{
				//Clear portals
				_portalA->SetEnabled(false);
				_portalB->SetEnabled(false);
			}
	}
	
    if(btnsdown & WPAD_BUTTON_2)
	{
		//Show/Hide Designator
		_portii->EnableLaser(!(_portii->IsLaserEnabled()));
	}

    if(btnsdown & WPAD_BUTTON_1)
	{
		//Clear portals
		_portalA->SetEnabled(false);
		_portalB->SetEnabled(false);
	}
	
	if(_won)
		return false;
	
    return true;
}

void Level::PreRender()
{
	background->Render(_screenOffsetX, _screenOffsetY);
	_randomJunk->Render(_screenOffsetX, _screenOffsetY);
}

bool Level::Render()
{
    //Draw the level
	
	_portalA->Render(_screenOffsetX, _screenOffsetY);
	_portalB->Render(_screenOffsetX, _screenOffsetY);
	
	//Draw Laser Sight
	if(_doDrawSight)
	{
		_portii->RenderSight(_sightX, _sightY, _screenOffsetX, _screenOffsetY);
        //dQ.Draw(_screenOffsetX, _screenOffsetY);
	}
	
	//DEBUG AIM SIGHT
    //dQ.Draw(_screenOffsetX, _screenOffsetY);
		
	//Draw cakes
	for(int i = 0; i < (int)_cakes.size(); i++)
		_cakes[i]->Render(_screenOffsetX, _screenOffsetY);
	
	_portii->Render(_screenOffsetX, _screenOffsetY);
	
	
	for(int x = -2000; x < 2000; x+= 640)
    {
		_fireSprite.SetPosition(x, WORLD_DEAD_HEIGHT);
		_fireSprite.Draw(_screenOffsetX, _screenOffsetY);
	}
	
	if(_cinematic) //cinematic
	{
		Quad bar;
		bar.SetFillColor((GXColor){0,0,0,255});
		bar.SetPosition(-2,-60);
		bar.SetHeight(100);
		bar.SetWidth(650);
		bar.Draw();
		bar.Draw();
		
		bar.SetPosition(-2,432);
		bar.Draw();
		bar.Draw();
	}

	_hud->Render();

	if(_dead)
	{
		RenderDead();
		return true;
	}

	if(_mode == GameMode::STORY)
	{
		if(_storyIndex < (int)_storyText.size())
		{
			int tp = rand() % 4;
		
		    if(tp == 0)
				_ptrSndManager->PlaySound(Sound::TYPEWRITER1_FX, 0);
			else if(tp == 1)
				_ptrSndManager->PlaySound(Sound::TYPEWRITER2_FX, 0);
			else if(tp == 2)
				_ptrSndManager->PlaySound(Sound::TYPEWRITER3_FX, 0);
			else if(tp == 3)
				_ptrSndManager->PlaySound(Sound::TYPEWRITER4_FX, 0);
				
			_textBubble->SetText(_storyText[_storyIndex]->GetData(), _storyText[_storyIndex]->GetData2());
			_textBubble->Render();
                        _textBubble->ClearText();
			_storyIndex++;
			
			return true;
		}
	}
	
	return false;
}

void Level::RenderDead()
{

	Sprite deadScreen;
	deadScreen.SetPosition(150,150);
	deadScreen.SetImage(_deadImage);
	
	deadScreen.Draw();
	
	_dead = false;
        Reset();
}

void Level::RenderSummary(bool highScore)
{
	//TODO have this print a summary showing level results.
	
	//For now, just print generic, "Level Complete!"
	
	Sprite levelComplete;
	levelComplete.SetPosition(150,150);
	levelComplete.SetImage(_completeImage);
	
	levelComplete.Draw();
	
	if(highScore)
	{
		Sprite scoreSprite;
		scoreSprite.SetPosition (30, 30);
		scoreSprite.SetImage(_scoreImage);
		
		scoreSprite.Draw();
	}
}

void Level::CheckCakeHits()
{
	_won = true;
	
	for(int i = 0; i < (int)_cakes.size(); i++)
	{
		if(_cakes[i]->IsAvailable())
		{
			_won = false;
			if(_cakes[i]->CheckCollision(_portii->GetSprite()))
			{
				_ptrSndManager->PlaySound(Sound::CAKE_FX, 0);
				_cakes[i]->SetAvailable(false);
                                _hud->SetCakeObtained(_hud->GetCakeObtained() + 1);
			}
		}
	}
}

bool Level::PortalWillStick(Pointer* ptr)
{
    Sprite tSprite;
    tSprite.SetPosition(ptr->GetCenterX() - _screenOffsetX, ptr->GetCenterY() - _screenOffsetY);
    tSprite.SetImage(ptr->GetCenterImage());

	//First check non portal wall space
	for(int i = 0; i < (int)_npZones.size(); i++)
	{
		if(tSprite.CollidesWith(_npZones[i]->GetRectangle()))
			return false;
	}
	
	//Do a check for trans Ground (Portals wont stick to this, but can go through)
	for(int i = 0; i < (int)_transGround.size(); i++)
	{
		if(tSprite.CollidesWith(_transGround[i], true))
			return false;
	}

	//Right now don't allow portals on ground walls.
	for(int i = 0; i < (int)_ground.size(); i++)
	{
		if(tSprite.CollidesWith(_ground[i], true))
			return false;
	}
	
        //return true;

	//Todo line trace to see if it will hit a solid wall
	float _height = 4;
	float _width = sqrt((_portii->GetWorldX() - tSprite.GetX())* (_portii->GetWorldX() - tSprite.GetX()) +
					(_portii->GetWorldY() - tSprite.GetY())* (_portii->GetWorldY() - tSprite.GetY()));
	
	Sprite tRay;
	tRay.SetImage(ptr->GetCenterImage());
	tRay.SetPosition(_portii->GetWorldX(), _portii->GetWorldY());
	tRay.SetStretchWidth(_width);
	tRay.SetRotation(_portii->GetAimAngle()/2.0);
	
	//DEBUG Ray Tracing
	dQ.SetHeight(_height);
	dQ.SetWidth(_width);
	dQ.SetPosition(_portii->GetWorldX(), _portii->GetWorldY());
	dQ.SetRotation(_portii->GetAimAngle()/2.0);
	dQ.SetFillColor((GXColor){0,255,0,255});

	//End DEBUG
	
	//This check is to see if the ray tries to pass through any walls
	for(int i = 0; i < (int)_ground.size(); i++)
	{
		if(_ground[i]->CollidesWith(&tRay,true))
		{
			//return false;
		}
	}
	
	return true;
}

Sprite* Level::GetPortalSprite(Pointer* ptr)
{
    Sprite tSprite;
    tSprite.SetPosition(ptr->GetCenterX() - _screenOffsetX, ptr->GetCenterY() - _screenOffsetY);
    tSprite.SetImage(ptr->GetCenterImage());
	
	//This check is to see if the ray tries to pass through any walls
	for(int i = 0; i < (int)_ground.size(); i++)
	{
		if(tSprite.CollidesWith(_ground[i], true))
		{
			//Turn this off to get the code to stick portals to walls.
			//return _ground[i];
                         return NULL;
		}
	}
	
	return NULL;
}
		
bool Level::CheckForDeadPlayer()
{
	//Play can only die by falling into the 'fire'
	if(_portii->GetWorldY() > WORLD_DEAD_HEIGHT)
	{
	    _dead = true;
		_ptrSndManager->PlaySound(Sound::DEAD_FX, 0);
		return true;
	}
	
	_dead = false;	
	//TODO If player dies, need to play 'death fx' and prompt them to:
		//restart level
		//quit

	return false;
}

